package com.comunity_cloud.client.model.upload_file;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;
import java.nio.file.Files;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import com.comunity_cloud.client.model.CommunityFile;
import com.comunity_cloud.client.model.Configuration;
import com.comunity_cloud.client.model.MainModel2;
import com.comunity_cloud.client.model.communication.ConnectionsManager;
import com.comunity_cloud.client.model.communication.PacketListener;
import com.comunity_cloud.client.model.communication.PeerConnection;
import com.comunity_cloud.client.model.communication.packet.PeerPacket;
import com.comunity_cloud.client.model.communication.packet.PeerPacketHeader.Type;
import com.user_hosted_storage.dto.CommunityDTO;
import com.user_hosted_storage.dto.CommunityMemberDTO;

public class UploadFilesManager extends Thread implements PacketListener<UploadFileHeader>{

	private Map<Long, Vector<LocalFilesListener>> localFileListeners = new Hashtable<Long,Vector<LocalFilesListener>>();
	
	private Map<CommunityFile, Vector<FileStatusListener>> listeners = new Hashtable<CommunityFile, Vector<FileStatusListener>>();
	
	private static class FileInfo {
		FileOutputStream out;
		long written;
	}
	
	private Map<String, FileInfo> files = new Hashtable<>();

	private static final long  RELOAD_INTERVAL = 1000;
	
	public UploadFilesManager() {
		PeerConnection.addPacketListener(Type.UPLOAD_FILE, this);
		start();
	}
	
	@Override
	public void run() {
		while(true) {
			Collection<CommunityDTO> communities = MainModel2.getInstance().getCommunities();
			if(communities.isEmpty()) {
				timedWait(RELOAD_INTERVAL);
				continue;
			}
			
			for (CommunityDTO communityDTO : communities) {
				
				File communitySyncFolder = new File(Configuration.getInstance().communityPath+communityDTO.getName());
				communitySyncFolder.mkdirs();
				File[] files = communitySyncFolder.listFiles();
				if(files.length == 0) {
					timedWait(RELOAD_INTERVAL);
					break;
				}
				List<CommunityFile> communityFiles = new LinkedList<>();
				for (File file : files) {
					
					try {
						
						if(isFileLocked(file)){
							continue;
						}
						
						CommunityFile communityFile = new CommunityFile(communityDTO);
						communityFile.setPath(file.getName());
						communityFile.setLength(file.length());
						communityFile.setState(CommunityFile.State.PENDING);
//						communityFile.setImage(FileSystemUtils.getFileIcon(file)); 
//						communityFile.setTypeDesc(FileSystemUtils.getFileTypeDesc(file));
						List<LocalFilesListener> localFileListenersList = localFileListeners.get(communityDTO.getId());
						if(localFileListenersList != null) {
							for (LocalFilesListener localFilesListener : localFileListenersList) {
								localFilesListener.fileAdded(communityFile);
							}
						}
						
						communityFiles.add(communityFile);
					} catch (Exception e) {
						e.printStackTrace();
					} 
			        
				}
				
				UploadFileHeader uploadFileHeader = new UploadFileHeader();
				int readBufferSize = Configuration.getInstance().readBufferSize;
				byte[] payloadBuffer = new byte[readBufferSize];
				
				PeerPacket<UploadFileHeader> peerPacket = new PeerPacket<>(uploadFileHeader,payloadBuffer);
				
				for (CommunityFile communityFile : communityFiles) {
					communityFile.setState(CommunityFile.State.IN_PROGRESS);
					FileInputStream fileStream = null;
					
					String filePath = Configuration.getInstance().communityPath+communityDTO.getName()+File.separator+communityFile.getPath();
					
					try {
						List<FileStatusListener> fileStatusListeners = listeners.get(communityFile);
						if(fileStatusListeners != null) {
							for (FileStatusListener fileStatusListener : fileStatusListeners) {
								fileStatusListener.statusChanged(communityFile, CommunityFile.State.IN_PROGRESS);
							}
						}
						
						fileStream = new FileInputStream(filePath);
						
						List<CommunityMemberDTO> hosts = MainModel2.getInstance().getOnlineHosts(communityDTO);
						
						LinkedList<CommunityMemberDTO> fullHosts = new LinkedList<CommunityMemberDTO>();
						
						fullHosts.addAll(hosts);
						
						fullHosts.add(MainModel2.getInstance().getCommunityMember(communityDTO.getId(), MainModel2.getInstance().getClientDTO().getId()));
						
						Collections.sort(fullHosts, new Comparator<CommunityMemberDTO>() {

							@Override
							public int compare(CommunityMemberDTO o1, CommunityMemberDTO o2) {
								return ((Long)o1.getId()).compareTo(o2.getId());
							}
						});
						
						long fileSize = communityFile.getLength();
						long chunkSize = Math.round((double)fileSize/ fullHosts.size());
						
						long totalSent = 0;
						
						for (int i = 0; i < fullHosts.size(); i++) {
							
							CommunityMemberDTO host = fullHosts.get(i);
							
							int nextIndex = i + 1;
							
							if(nextIndex == fullHosts.size()) {
								nextIndex = 0;
							}
							
							CommunityMemberDTO redundantHost = fullHosts.get(nextIndex);
							
							uploadFileHeader.setCommunityId(communityDTO.getId());
							uploadFileHeader.setTotalOffset(i*chunkSize);
							uploadFileHeader.setTotalSize(fileSize);
							
							uploadFileHeader.setChunkSize(i*chunkSize+chunkSize > fileSize ? fileSize - i*chunkSize:chunkSize);
							uploadFileHeader.setFileName(communityFile.getPath());
							communityFile.updateChunk(uploadFileHeader.getTotalOffset(), true, host, uploadFileHeader.getChunkSize());
							
							communityFile.updateChunk(uploadFileHeader.getTotalOffset(), false, redundantHost, uploadFileHeader.getChunkSize());
							
							List<LocalFilesListener> localFileListenersList = localFileListeners.get(communityDTO.getId());
							if(localFileListenersList != null) {
								for (LocalFilesListener localFilesListener : localFileListenersList) {
									localFilesListener.fileAdded(communityFile);
								}
							}
							
							boolean primaryIsMe = host.getClient().getId() == MainModel2.getInstance().getClientDTO().getId();
							
							PeerConnection peerConnection = null;
							if (!primaryIsMe) {
								peerConnection = ConnectionsManager
										.getInstance().getPeerConnection(
												host.getClient().getId());
							}
							
							boolean redundantIsMe = redundantHost.getClient().getId() == MainModel2.getInstance().getClientDTO().getId();
							PeerConnection redundantPeerConnection = null;
							
							if (!redundantIsMe) {
								redundantPeerConnection = ConnectionsManager.getInstance().getPeerConnection(redundantHost.getClient().getId());
							}
							
							long totalChunklRead = 0;
							while(true) {
								
								int read = fileStream.read(payloadBuffer, 0, (int) (chunkSize - totalChunklRead >= readBufferSize? readBufferSize:chunkSize - totalChunklRead));
								
								if(read == -1 || totalChunklRead == chunkSize) {
									break;
								}
								
								uploadFileHeader.setOffset(totalChunklRead);
								uploadFileHeader.setPayloadSize(read);
								uploadFileHeader.setPrimary(1);
								
								if (!primaryIsMe) {
									peerConnection.sendPacket(peerPacket);
								} else {
									packetReceived(peerPacket);
								}
								
								uploadFileHeader.setPrimary(0);
								if (!redundantIsMe) {
									redundantPeerConnection.sendPacket(peerPacket);
									UploadFileHeader testHeader = new UploadFileHeader();
									testHeader.fromByteArray(peerPacket.getHeader().asByteArray());									
								} else {
									packetReceived(peerPacket);
								}
								totalChunklRead += read;
								totalSent += read;
								fileStatusListeners = listeners.get(communityFile);
								if(fileStatusListeners != null) {
									for (FileStatusListener fileStatusListener : fileStatusListeners) {
										fileStatusListener.onProgressChange(communityFile, (int) (((double)totalSent/fileSize)*100));
										if(totalSent == fileSize) {
											communityFile.setState(CommunityFile.State.SYNCHRONIZED);
											fileStatusListener.statusChanged(communityFile, CommunityFile.State.SYNCHRONIZED);
										}
									}
								}
								
							}
						}
						
					} catch (IOException e) {
						e.printStackTrace();
					} finally {
						try {
							if (fileStream != null) {
								fileStream.close();
							}
						} catch (IOException e) {
							e.printStackTrace();
						}
						try {
							Files.delete(new File(filePath).toPath());
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				}
			}
		}
	}

	private boolean isFileLocked(File file) {
		RandomAccessFile out = null;
		FileLock lock = null;
		try {
			out = new RandomAccessFile(file,"rw");
			lock = out.getChannel().tryLock();
			if(lock == null) {
				return true;
			}
			return false;
		} catch (Exception e) {
			return true;
		} finally {
			if(lock != null){
				try {
					lock.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (out != null) {
				try {
					out.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	private void timedWait(long reloadInterval) {
		synchronized (this) {
			try {
				wait();
			} catch (InterruptedException e) {}
		}
	}

	@Override
	public void packetReceived(PeerPacket<UploadFileHeader> peerPacket) {
		
		try {
			UploadFileHeader header = peerPacket.getHeader();
			
			FileInfo file = files.get(header.getCommunityId()+header.getFileName());
			
			if(file == null){
				file = new FileInfo();
				String fileName = Configuration.getInstance().hostedFilesPath+MainModel2.getInstance().getCommunity(header.getCommunityId()).getName()+File.separator+header.getTotalOffset()+header.getFileName();
				
				file.out = new FileOutputStream(fileName);
				
				
				FileChunkHeader fileChunkHeader = new FileChunkHeader(header.getTotalSize(), header.getTotalOffset(),header.getPrimary());
				file.out.write(fileChunkHeader.asBytes());
				
				files.put(header.getCommunityId()+header.getFileName(), file);
			}
			
			file.out.write(peerPacket.getPayload(), 0, header.getPayloadSize());
			file.written += header.getPayloadSize();
			if (file.written == header.getChunkSize()) {
				file.out.close();
				files.remove(header.getCommunityId()+header.getFileName());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public void addLocalFilesListener(long communityId, LocalFilesListener listener){
		Vector<LocalFilesListener> communityLocalFilesListeners = localFileListeners.get(communityId);
		if(communityLocalFilesListeners == null) {
			communityLocalFilesListeners = new Vector<>();
			localFileListeners.put(communityId, communityLocalFilesListeners);
		}
		if (!communityLocalFilesListeners.contains(listener)) {
			communityLocalFilesListeners.add(listener);
		}
	}
	
	public void removeLocalFilesListener(long communityId, LocalFilesListener listener) {
		Vector<LocalFilesListener> communityLocalFilesListeners = localFileListeners.get(communityId);
		if(communityLocalFilesListeners == null) {
			return;
		}
		communityLocalFilesListeners.remove(listener);
	}
	
	public void addFileStatusListener(CommunityFile communityFile, FileStatusListener fileStatusListener) {
		Vector<FileStatusListener> listenersList = listeners.get(communityFile);
		if(listenersList == null) {
			listenersList = new Vector<>();
			listeners.put(communityFile, listenersList);
		}
		
		if (!listenersList.contains(fileStatusListener)) {
			listenersList.add(fileStatusListener);
		}
	}
	
}
